
#ifndef TORRENT_HTP
#define TORRENT_HPP

#include "libtorrent/session.hpp"
#include "libtorrent/torrent_handle.hpp"
#include "libtorrent/magnet_uri.hpp"

#include <string>
#include <vector>
#include <iostream>
#include <sstream>

#include <vector>
#include <thread>
#include <chrono>
#include <memory>

void read_alerts(std::vector<lt::alert*> &alerts) {
    for (lt::alert const* a : alerts) { 
        // alert examples on libtorrent do not work.
        // https://www.libtorrent.org/tutorial-ref.html
        // results in: compilation error - incomplete type
        // type code referenced from: 
        // ./deps/libtorrent/include/libtorrent/alert_types.hpp
        switch (a -> type()) {
            default:
                std::cout << a -> message() << ", " << a -> type() << std::endl;
//             case 11: // tracker unreachable
//             case 67: // torrent added
//             case 23:
//             case 26: // torrent finished alert
//             case 64: // torrent error alert
        }
    }
    return;
}

std::vector<std::string> readfile(std::string manifest) {
    std::vector<std::string> out;
    std::fstream fin;
    fin.open(manifest);
    std::string line;
    if (!fin.is_open()) {
        fprintf(stderr, "Error: could not open file.\n");
    }
    while (fin) {
        std::getline(fin, line); 
        out.push_back(line);
    }
    return out;
}

std::string scrolling_str(std::string s, const unsigned int LEN_MAX) {
    static int i = 0;
    std::string shorter;
    if (s.length()  > LEN_MAX) {
        if ((i + LEN_MAX) < s.length()) {
            try {
                shorter = s.substr(i, LEN_MAX);
            } catch (std::out_of_range const &e) {
                ;
            }
            i = (i + 1) % s.length();
        } else {
            i = 0;
        }
    } else {
        shorter = s;
    }
    return shorter;
}

void display(lt::torrent_status &ts) {
    // <download_rate> downloading <name> from <num_peers> (<num seeds> seeds)
    // <upload rate> uploading <name> to <num_peers> (<num seeds> seeds)
    // fuck cout
    static char last_state = 0;
    // don't bother line breaking for checks and meta data'
    if (last_state != ts.state) {
        std::cout << std::endl;
    }
    // long names will spam console
    #define MAX_LENGTH 40
    std::string shortname = scrolling_str(ts.name, MAX_LENGTH);
    const char *tname = shortname.c_str();
    float perc = ts.progress * 100;
    float down = ts.download_rate/1000000;
    float up   = ts.upload_rate/1000000;
    std::int64_t total_upload  = ts.total_upload/1000000;
    switch(ts.state) {
        case 1: // checking files
            fprintf(stdout, "checking files\r");
            break;
        case 2: // downloading meta data
            fprintf(stdout, "downloading meta data\r");
            break;
        case 3: // downloading
            fprintf(stdout, "[%2.2f%] Downloading %s from %i peers (%i seeds) @ %2.2f MB/s\r", 
                perc, tname, ts.num_peers, ts.num_seeds, down);
            break;
        case 4: // finished
            break;
        case 5: // seeding
            fprintf(stdout, "Uploading %s to %i/%i peers (%i seeds) @ %2.2f MB/s [%i MB]\r", 
                tname, ts.num_peers, ts.list_peers, ts.list_seeds, up, total_upload);
            break;
    }
    last_state = ts.state;
}

// magnet link version
//     lt::add_torrent_params params = lt::parse_magnet_uri(magnet_link);
//     params.save_path = "."; // save in current dir
//     lt::torrent_handle h = s.add_torrent(params);

    // torrents = paths to .torrent files
void init_torrents(lt::session &s, const std::vector<std::string>& torrents) {
    std::string torrent;
    for (unsigned int i = 0; i < torrents.size(); ++i) {
        torrent = torrents[i];
        if (torrent.length() < 2) { // less than 1 char and newline
            continue;
        }
        lt::add_torrent_params p;
        p.save_path = "./downloaded";
        try {
            p.ti = std::make_shared<lt::torrent_info>(torrent);
        } catch (std::exception const&e) {
            std::cerr << "Encountered error while reading torrent files ("
                    << torrent
                    << ").\n" 
                    << e.what() << std::endl;
            return;
        }
        s.add_torrent(p);
    }
}

// run torrents in the background
void seedbox (std::string &torrentfiles) {
    #define MINUTE 60000
    lt::session s;
    std::vector<std::string> paths = readfile(torrentfiles.c_str());
    if (paths.empty()) {
        return;
    }
    init_torrents(s, paths);
    std::vector<lt::torrent_handle> handles = s.get_torrents();
    const int TORRENT_COUNT = handles.size();
    unsigned int not_dl_count = 0;
    unsigned int tid = 0;
    unsigned int elapsed = 0;
    while(true) {
        std::vector<lt::alert*> alerts;
        s.pop_alerts(&alerts);
        read_alerts(alerts);
        for (unsigned int i = 0; i < TORRENT_COUNT; ++i) {
            lt::torrent_status ts = handles[i].status();
            if (!ts.is_seeding && !ts.is_finished) {
                not_dl_count++;
            }
        }
        // if no torrents are downloading
        if (!(TORRENT_COUNT > not_dl_count)) {
            lt::torrent_status spotlight = handles[tid].status();
            display(spotlight);
            if (elapsed >= MINUTE) {
                std::cout << std::endl;
                tid = (tid + 1) % TORRENT_COUNT;
            }
        }
        elapsed = (elapsed + 1) % (MINUTE + 1);
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
//         std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

#include <curlpp/cURLpp.hpp>
#include <curlpp/Easy.hpp>
#include <curlpp/Options.hpp>
std::string request(const std::string& url) {
    curlpp::options::Url source (url);
    curlpp::Easy request;
    request.setOpt(source);
    std::ostringstream response;
    try {
        response << request;
    } catch (curlpp::LibcurlRuntimeError e) {
        std::cout << e.what() << std::endl;
    } catch (std::exception const& e) {
        std::cout << e.what() << std::endl;
    }
    return response.str();
}

// #include "libtorrent/create_torrent.hpp"
// #include "libtorrent/entry.hpp"
// #include "libtorrent/bencode.hpp"
#include "peertube.hpp" // also includes rapidjson

void torrent_basic_loop(std::string torrent_file) {
    lt::session s;
    lt::add_torrent_params p;
    p.save_path = "./downloaded";
    p.ti = std::make_shared<lt::torrent_info>(torrent_file);
    lt::torrent_handle h = s.add_torrent(p);
    h.set_flags(lt::torrent_flags::sequential_download);
    lt::torrent_status ts;
    do {
        std::vector<lt::alert*> alerts;
        s.pop_alerts(&alerts);
        read_alerts(alerts);
        ts = h.status();
        display(ts);
        std::this_thread::sleep_for(std::chrono::seconds(1));
    } while (!ts.is_seeding && !ts.is_finished);
}

// download torrent file from peertube server
std::string dl_torrentfile(struct file_t *video_file, const char * title = NULL) {
    if (video_file == NULL) {
        return std::string();
    }
    if (video_file -> attribs == NULL) {
        return std::string();
    }
    std::string torrent_url (video_file -> attribs[TORRENTDOWNLOAD_URL]);
    std::string buffer = request(torrent_url);
    std::string filename;  
    {
        std::stringstream ss;
        if (title == NULL) { // use the file name from the url
            unsigned int pos = torrent_url.rfind("/");
            pos++; // +1 to omit '/'
            ss << torrent_url.substr(pos, torrent_url.size());
        } else {
        // does not remove forbidden characters in file name yet
            ss << title <<"-"<< video_file ->  resolution << "p.torrent";
        }
        filename = ss.str();
        std::cout << filename << std::endl;
    }
    std::fstream fout;
    fout.open (filename, std::fstream::out | std::fstream::binary);
    if (fout.good()) {
        fout << buffer;
    }
    fout.close();
    return filename;
}

// watch a video given a URL
bool watch(std::string video_url) {
    //turn peertube video url into api request
    bool is_playlist = false;
    std::string api  = get_endpoint(video_url, is_playlist);
    if (api.length() == 0) {
        std::cout << "Error: can't watch video." << std::endl;
        return false;
    }
    // figure something out with playlists.
    if (is_playlist) {
        std::cout << "playlist support not implemented" << std::endl;
        return false;
    }
    std::string got  = request(api);
    std::cout << api << std::endl;
    rapidjson::Document root;
    root.Parse(got.c_str());
    // video downloads can either be in "files"
    // or under "streamingPlaylists" if HLS is enabled on the server
    struct video_t *video = init_from_json(root);
    video_print(video);
    // get torrent download url
    struct file_t *best_file = video_pick_file(video);
    // torrent file > magnet link if peertube instance's tracker is unreliable
    std::string saved = dl_torrentfile(best_file);
    if (!saved.empty()) {
        std::cout << "acquired .torrent file! " << saved << std::endl;
        torrent_basic_loop(saved);
    }
    std::cout << "\n\n\n" << std::endl;
    video_free(video);
    return true;
}

#endif
